package nl.unimaas.games.tron.experiment;

import java.io.File;
import java.io.FilenameFilter;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;
import java.util.logging.ConsoleHandler;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import nl.unimaas.games.tron.TronGame;
import nl.unimaas.games.tron.util.logging.LogUtils;

public class ExperimentExecutor {
	/** Current experiment */
	private Experiment exp;
	private final static Level DEFAULT_LEVEL = Level.INFO;
	private final Queue<Experiment> experiments = new LinkedList<Experiment>();
	private final static String DEFAULT_SAVE_DIR = "results";
	private String saveDir = DEFAULT_SAVE_DIR;
	private volatile boolean isRunning = false;
	private Logger logger;
	private Thread thread;
	private final static boolean DEBUG_OUTPUT = false;
	
	public ExperimentExecutor() {
		this(new String[0]);
	}
	
	public ExperimentExecutor(String[] paths) {
		init();
		//load the experiments
		for (int i = 0; i < paths.length; i++) {
			Experiment exp;
			try {
				exp = Experiment.fromFile(paths[i]);
				exp.DEBUG_OUTPUT = DEBUG_OUTPUT;
			}
			catch (Exception e) {
				logger.severe(String.format("Unable to load experiment '%s': %s. \n", paths[i], e.getMessage()));
				continue;
			}
			experiments.add(exp);
		}
	}
	
	public ExperimentExecutor(String path) {
		this(new String[] {path});
	}
	
	private void init() {
		Runtime.getRuntime().addShutdownHook(new Thread() {
		    public void run() {
		    	
		    }
		});
	}
	
	public static void main(String[] args) {
		boolean stayOpen = (args.length == 0);
		System.out.println(String.format("* Initial heap size: %dM	Max heap size: %dM", Runtime.getRuntime().totalMemory() / (1000 * 1000), Runtime.getRuntime().maxMemory() / (1000 * 1000)));

		do {
			if (args.length == 0) {
				System.out.println("Command:");
				Scanner scanner = new Scanner(System.in);
				String cmd = scanner.nextLine();
				Pattern p = Pattern.compile("\"(?:(?<=\")([^\"]+)\"\\s*)|\\s*([^\"\\s]+)");
				Matcher m = p.matcher(cmd);
				ArrayList<String> argList = new ArrayList<String>();
				while (m.find()) {
					String arg = m.group(0);
					if (arg.startsWith("\""))
						arg = arg.substring(1);
					if (arg.endsWith("\""))
						arg = arg.substring(0, arg.length() - 1);
					argList.add(arg);
				}
				args = argList.toArray(new String[argList.size()]);
			}
			
			if (args[0].equalsIgnoreCase("exit") || args[0].equalsIgnoreCase("quit")) {
				System.out.println("Goodbye.");
				System.exit(0);
				return;
			}
			else if (args[0].equalsIgnoreCase("-e")) {
				int src = 1;
				System.out.println("Running experiments:");
				String[] paths = new String[args.length - src];
				System.arraycopy(args, src, paths, 0, paths.length);
				for (int i = 0; i < paths.length; i++) {
					String path = paths[i];
					if (path.startsWith(" ")) {
						path = path.substring(1);
					}
					System.out.println("    " + path);
					paths[i] = path;
				}
				runExperiments(paths);
			}
			else if (args[0].equalsIgnoreCase("-ed")) {
				String path = args[1];
				String[] files;
				File dir = new File(path);
				files = dir.list(filter);
				for (int j = 0; j < files.length; j++) {
					files[j] = path + File.separator + files[j];
					System.out.println("  Loaded experiment: '" + files[j] + "'");
				}
				runExperiments(files);
			}
			else if (args[0].equalsIgnoreCase("-r")) {
				System.out.println("- Replay mode -");
				TronGame.main(args);
				return;
			}
			args = new String[0];
			System.out.println();
		} while (stayOpen);
	}

	private final static FilenameFilter filter = new FilenameFilter() {
		@Override
		public boolean accept(File dir, String name) {
			return name.endsWith(".xml");
		}
	};
	
	public static void runExperiments(String[] paths) {
		ExperimentExecutor executor = new ExperimentExecutor(paths);
		
		Logger logger = Logger.getLogger("ExperimentExecutor");
		ConsoleHandler h = LogUtils.getConsoleHandler(logger);
		if (h != null)
			h.setFormatter(LogUtils.tinyFormatter);
		executor.setLogger(logger);
		LogUtils.setLoggerLevel(logger, DEFAULT_LEVEL);
		
		executor.start();
		logger.info("All done!");
		System.exit(0);
	}
	
	/** Blocking method. runs the next experiment on the current thread */
	public void runNextExperiment() {
		if (!hasExperimentsToRun())
			throw new IllegalStateException("no more experiments left to run");
		
		exp = experiments.poll();
		
		//configure experiment
		exp.setLogger(logger);
		exp.setResultDirectory(saveDir + String.format("/%s", exp.getId()));
		logger.info("Starting experiment " + exp.toString());
		exp.run();
		logger.info("Done running experiment " + exp.toString());
	}
	
	public boolean hasExperimentsToRun() {
		return (!experiments.isEmpty());
	}
	
	public void addExperiment(Experiment exp) {
		if (isRunning)
			throw new IllegalStateException("an experiment is running");
		experiments.add(exp);
	}
	
	/** Clears the experiment queue */
	public void clearExperiments() {
		experiments.clear();
	}
	
	public synchronized void start() {
		if (!hasExperimentsToRun())
			throw new IllegalStateException("no experiments to run!");
		if (isRunning)
			throw new IllegalStateException("an experiment is already running");
		isRunning = true;
		
		while (hasExperimentsToRun()) {
			runNextExperiment();
		}
	}
	
	public synchronized void startAsync() {
		thread = new Thread(experimentRunnable);
		thread.start();
	}
	
	/** Can only be called if we started using startAsync() */
	public void cancel() {
		if (thread == null)
			return;
		
		try {
			thread.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		isRunning = false;
	}

	public void saveResults() {
		if (exp == null)
			throw new IllegalStateException("no experiment to save");
	}
	
	public void setSavePath(String path) {
		this.saveDir = path;
	}
	
	public void setLogger(Logger log) {
		this.logger = log;
	}
	
	private Runnable experimentRunnable = new Runnable() {
		@Override
		public void run() {
			start();
		}
	};
}
